package com.xhj.mybatis.builder;

import com.xhj.mybatis.mapping.ParameterMapping;
import com.xhj.mybatis.mapping.SqlSource;
import com.xhj.mybatis.parsing.GenericTokenParser;
import com.xhj.mybatis.parsing.TokenHandler;
import com.xhj.mybatis.reflection.MetaClass;
import com.xhj.mybatis.reflection.MetaObject;
import com.xhj.mybatis.session.Configuration;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * SqlSourceBuilder
 *
 * @author XJks
 * @description SqlSourceBuilder 类用于解析 MyBatis 映射文件中的 SQL 语句，将其中的参数占位符（如 #{}）解析为 JDBC 的 ? 占位符，并生成参数映射列表。它负责将原始 SQL 和参数类型等信息转换为可执行的 SqlSource 对象，便于后续 SQL 执行和参数绑定。
 */
public class SqlSourceBuilder extends BaseBuilder {

    /**
     * 定义一个常量，包含所有允许的参数属性名，用逗号分隔
     */
    private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

    /**
     * 构造方法，接收 MyBatis 的配置对象
     *
     * @param configuration MyBatis 全局配置
     */
    public SqlSourceBuilder(Configuration configuration) {
        super(configuration);
    }

    /**
     * 解析 SQL 语句，将 #{...} 占位符替换为 ? 并生成参数映射
     *
     * @param originalSql          原始 SQL 语句
     * @param parameterType        参数类型
     * @param additionalParameters 额外参数
     * @return SqlSource 对象
     */
    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        // 创建参数映射处理器
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        // 创建通用的 Token 解析器，处理 #{...} 占位符
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql;
        // 判断是否需要压缩 SQL 里的多余空白字符
        if (configuration.isShrinkWhitespacesInSql()) {
            sql = parser.parse(removeExtraWhitespaces(originalSql));
        } else {
            sql = parser.parse(originalSql);
        }
        // 返回静态 SQL 源对象，包含解析后的 SQL 和参数映射
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }

    /**
     * 移除字符串中的多余空白字符，只保留单词之间一个空格
     *
     * @param original 原始字符串
     * @return 处理后的字符串
     */
    public static String removeExtraWhitespaces(String original) {
        // 创建一个分词器，按空白字符（空格、制表符等）分割字符串
        StringTokenizer tokenizer = new StringTokenizer(original);
        // 用于拼接处理后的字符串
        StringBuilder builder = new StringBuilder();
        // 判断是否还有下一个单词
        boolean hasMoreTokens = tokenizer.hasMoreTokens();
        // 循环处理每个单词
        while (hasMoreTokens) {
            // 取出下一个单词并添加到结果中
            builder.append(tokenizer.nextToken());
            // 更新是否还有下一个单词
            hasMoreTokens = tokenizer.hasMoreTokens();
            // 如果还有下一个单词，则在单词之间加一个空格
            if (hasMoreTokens) {
                builder.append(' ');
            }
        }
        // 返回处理后的字符串，单词之间只保留一个空格
        return builder.toString();
    }

    /**
     * 内部类：参数映射处理器，实现 TokenHandler 接口
     * 负责解析 #{...} 占位符内容，生成参数映射
     */
    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

        /**
         * 存储参数映射的列表
         */
        private List<ParameterMapping> parameterMappings = new ArrayList<>();

        /**
         * 参数类型
         */
        private Class<?> parameterType;

        /**
         * 额外参数的元对象
         */
        private MetaObject metaParameters;

        /**
         * 构造方法
         *
         * @param configuration        MyBatis 配置
         * @param parameterType        参数类型
         * @param additionalParameters 额外参数
         */
        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
            super(configuration);
            this.parameterType = parameterType;
            this.metaParameters = configuration.newMetaObject(additionalParameters);
        }

        /**
         * 获取参数映射列表
         *
         * @return 参数映射列表
         */
        public List<ParameterMapping> getParameterMappings() {
            return parameterMappings;
        }

        /**
         * 处理 #{...} 占位符内容
         *
         * @param content 占位符内部内容
         * @return 返回 ? 占位符
         */
        @Override
        public String handleToken(String content) {
            // 解析并添加参数映射
            parameterMappings.add(buildParameterMapping(content));
            // SQL 中用 ? 占位
            return "?";
        }

        /**
         * 构建参数映射对象
         *
         * @param content 占位符内容
         * @return 参数映射对象
         */
        private ParameterMapping buildParameterMapping(String content) {
            // 解析属性
            Map<String, String> propertiesMap = parseParameterMapping(content);
            // 获取属性名
            String property = propertiesMap.get("property");
            // 确定 ParameterMapping 对应的参数类型
            Class<?> propertyType;
            // todo:源码中还有其他处理逻辑，这里简化处理，待完善。
            if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
                // 如果 typeHandlerRegistry 有该参数类型的处理器，则直接使用该参数类型
                propertyType = parameterType;
            } else if (property != null) {
                // 如果属性名不为空，则使用反射框架获取属性类型
                MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
                if (metaClass.hasGetter(property)) {
                    propertyType = metaClass.getGetterType(property);
                } else {
                    propertyType = Object.class;
                }
            } else {
                // 其他情况，回退使用 Object 类型
                propertyType = Object.class;
            }
            // 构建并返回参数映射对象
            ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
            return builder.build();
        }

        /**
         * 解析 #{...} 占位符内容为属性 map。
         * TODO:
         *  1.这里需要将 content 解析为 Map<String, String> 类型，是因为 #{...} 占位符内部可以包含多个参数属性，例如：{@code #{id, javaType=int, jdbcType=NUMERIC}}
         *  2.解析为 Map 后，可以方便地通过属性名（如 javaType、jdbcType、property 等）获取对应的属性值，便于后续构建参数映射对象 ParameterMapping。这样可以灵活支持多种参数配置和扩展。
         *
         * @param content 占位符内容
         * @return 属性 map
         */
        private Map<String, String> parseParameterMapping(String content) {
            try {
                // 解析表达式
                return new ParameterExpression(content);
            } catch (RuntimeException ex) {
                throw ex;
            } catch (Exception ex) {
                // 解析失败，抛出异常并提示语法
                throw new RuntimeException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
            }
        }
    }

}

